using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Microblaze.Simulator.Engine;

using Weazel.Gezel.Model;
using Weazel.Gezel.Simulator.Reference;
using Weazel.Gezel.Simulator.Reference.IpBlocks;

namespace Weazel.Microblaze.Simulator.Components.Gezel
{
    public class OpbGezelComponent : Component
    {
        private Opb.Opb opb;
        public Opb.Opb Opb
        {
            get { return opb; }
        }

        private Model gezelModel;
        private Simulation gezelSimulation;

        List<GezelOpbMaster> masterIpBlocks;
        List<GezelOpbSlave> slaveIpBlocks;

        public OpbGezelComponent(Simulator.Engine.SimulationContext context, Opb.Opb opb, Model gezelModel)
            : base(context)
        {            
            this.masterIpBlocks = new List<GezelOpbMaster>();
            this.slaveIpBlocks = new List<GezelOpbSlave>();

            this.opb = opb;
            this.gezelModel = gezelModel;
            Initialize();
        }

        private GezelOpbSlave GetSlaveAtAddress(uint address)
        {
            foreach (GezelOpbSlave slave in slaveIpBlocks)
                if (slave.LowAddress <= address && slave.HighAddress > address)
                    return slave;

            return null;
        }

        private void Initialize()
        {
            Console.ForegroundColor = ConsoleColor.DarkRed;
            Console.WriteLine("Loading Gezel Component");
            Console.WriteLine("---------------------------------------------");
            Console.ForegroundColor = ConsoleColor.White;

            this.gezelSimulation = new Simulation();
            this.gezelSimulation.Setup(this.gezelModel);
            this.gezelSimulation.OutputWriter = Console.Out; // Context.Configuration.GezelOutputWriter;
            this.gezelSimulation.Reset();

            foreach (AbstractIpBlock ipBlock in gezelSimulation.IpBlocks.Values)
            {
                if (ipBlock is OpbMaster)
                {
                    OpbMaster master = (OpbMaster)ipBlock;
                    GezelOpbMaster masterComponent = new GezelOpbMaster(this, master);
                    masterIpBlocks.Add(masterComponent);

                    opb.RegisterMaster(masterComponent);
                }
                else if (ipBlock is OpbSlave)
                {
                    OpbSlave slave = (OpbSlave)ipBlock;
                    GezelOpbSlave slaveComponent = new GezelOpbSlave(this, slave);
                    slaveIpBlocks.Add(slaveComponent);

                    Console.WriteLine("Registering slave at: 0x{0:X}", slave.LowAddress);
                    opb.RegisterSlave(slaveComponent);
                }
                else
                {
                    throw new UnsupportedIpBlockException(ipBlock);
                }
            }
        }

        public void StepSimulation()
        {
            gezelSimulation.step();
            gezelSimulation.FlushOutput();
        }

        public bool RequestBusAccess(GezelOpbMaster master, bool request)
        {
            if (opb.Status == Weazel.Microblaze.Simulator.Components.Opb.Opb.OpbCycleStatus.Busy)
                return false;

            return opb.Request(master, request);
        }
        

        public override void Run()
        {
            while (true)
            {
                if (opb.Status == Weazel.Microblaze.Simulator.Components.Opb.Opb.OpbCycleStatus.Busy)
                {
                    bool isMaster = false;
                    foreach (GezelOpbMaster master in this.masterIpBlocks)
                    {
                        if (opb.CurrentMaster == master)
                        {                            
                            isMaster = true;
                            master.CheckPendingTransaction();
                        }
                    }

                    foreach (GezelOpbMaster master in this.masterIpBlocks)
                        opb.Request(master, false);

                    if(!isMaster)
                        StepSimulation();
                }
                else if (opb.Status == Weazel.Microblaze.Simulator.Components.Opb.Opb.OpbCycleStatus.Idle)
                {
                    // simulation is updated when the bus request is processed
                    // todo

                    StepSimulation();
                }

                WaitClock();
                
            }
        }

        public void BeginTransaction(Transaction transaction)
        {
        }

        public override string GetStatusString()
        {
            return "n/a";
        }
    }
}
